﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.IO;
using InvasionWars;



namespace IW_Server
{
    public partial class ServerLobby : Form
    {

        
        private IPAddress local;
        private Socket connection;
        const int defaultPort = 8500;
        private int clientsConnected;
        private BinaryWriter writer;
        private BinaryReader reader;
        private int clientPortOffset;



        private List<GameSession> gameSessions;

        public string ServerName
        {
            get
            {
                return serverName;
            }
        }
        private string serverName;

        public int Port
        {
            get
            {
                return port;
            }
        }
        private int port;

        private Thread serverThread;

        public ServerLobby()
        {
            InitializeComponent();
            
            //serverName = System.Environment.GetEnvironmentVariable("ComputerName");
            Trace.WriteLine(serverName,"ServerName");
            
            txtServerName.Text = serverName;
            port = defaultPort;
            txtPort.Text = port.ToString();
            displayMessage("server initialized");

            //listeners = new List<TcpListener>();
            local = IPAddress.Parse("127.0.0.1");

            serverThread = new Thread( new ThreadStart( RunServer) );
            serverThread.Start();
            clientPortOffset = 1;


            gameSessions = new List<GameSession>();
            

            



            //startServer();
        }

        private void txtPort_TextChanged(object sender, EventArgs e)
        {

        }

        private void btnRestart_Click(object sender, EventArgs e)
        {
            //try
            //{

            //    if (port != Convert.ToInt16(txtPort.Text))
            //    {
            //        if (MessageBox.Show("Restarting Server Services with a new Port Number will diconnect any Connected Clients", "Restart Server?", MessageBoxButtons.OKCancel) == DialogResult.OK)
            //        {
            //            //restart services;
            //            port = Convert.ToInt16(txtPort.Text);
            //            restartServer();
            //        }
            //    }
            //    else
            //        restartServer();
            //}
            //catch (FormatException fe)
            //{
            //    //notify that it is not a valid INT
            //    MessageBox.Show("Invalid Port Number - Restoring current value", "[Server] Lobby:Port Number");
            //    writeLog("Port is not a valid integer Number - restoring previous value");
            //    txtPort.Text = port.ToString();

            //}
            //catch (OverflowException ofe)
            //{
            //    //port number is too long.
            //    MessageBox.Show("Invalid Port Number - Restoring current value", "[Server] Lobby:Port Number");
            //    writeLog("Port number is too long - restoring previous value");
            //    txtPort.Text = port.ToString();
                
            //}
        }

        //threaded
        //private void listenForIncoming(int nPort)
        //{
        //    NetworkStream socketStream;
        //    BinaryWriter writer;
        //    BinaryReader reader;
        //    try
        //    {
                
        //        writeLog("Starting to listen on port " + nPort );
        //        TcpListener thisListener = new TcpListener(local, nPort);

        //        thisListener.Start();
                
                
        //        thisListener.AcceptSocket();
        //        writeLog("Accepted socket connection on " + port);

        //        socketStream = new NetworkStream(connection);
        //        writer = new BinaryWriter(socketStream);
        //        reader = new BinaryReader(socketStream);

        //    }
        //    catch (Exception any)
        //    {
        //        writeLog("server start failure:  " + any.Message + ": " + any.Source);

        //    }
        //}


        public void RunServer()
        {
            TcpListener defaultListener;
            
            NetworkStream socketStream;
            List<Thread> playerThreads = new List<Thread>();
            try
            {
                defaultListener = new TcpListener(local, defaultPort);
                

                while (true)
                {
                    defaultListener.Start();
                    displayMessage("Waiting for Connection");
                    int tempPort = (defaultPort + clientPortOffset);
                    playerThreads.Add(new Thread(new ThreadStart(playerThread)));
                    playerThreads[clientPortOffset - 1].Start();
                    connection = defaultListener.AcceptSocket();

                    displayMessage("Client connected");
                    //use this connection to the tell the client to connect on a different port
                    socketStream = new NetworkStream(connection);
                    displayMessage("Created socket Stream");
                    writer = new BinaryWriter(socketStream);
                    reader = new BinaryReader(socketStream);


                    
                    writer.Write((tempPort));
                    clientPortOffset += 1;
                    displayMessage("Sent Client new Port: " + (tempPort));

                    writer.Close();
                    reader.Close();
                    socketStream.Close();
                    connection.Close();
                    
                    defaultListener.Stop();

                    updateGameList();
                }


            }
            catch ( Exception any)
            {
                throw any;
            }
        }

        public void playerThread()
        {
            int playerPort = defaultPort + clientPortOffset;
            TcpListener playerListener;
            NetworkStream socketStream;
            BinaryReader playerReader;
            BinaryWriter playerWriter;
            Socket playerConn;
            bool connectionActive = true;

            
            playerListener = new TcpListener(local, playerPort);
            playerListener.Start();
            playerConn = playerListener.AcceptSocket();
            clientsConnected += 1;
            updateClientCounter();

            socketStream = new NetworkStream(playerConn);
                
            playerWriter = new BinaryWriter(socketStream);
            playerReader = new BinaryReader(socketStream);
            
            NetMessages nMsg;
            while (connectionActive)
            {
                try
                {
                    //playerWriter.Write(NetMessages.CHECK_IN + "");
                    nMsg = (NetMessages)playerReader.ReadByte();

                    switch (nMsg)
                    {
                        case NetMessages.CHECK_IN:
                            //acknowledge the client is connected
                            break;
                        case NetMessages.CREATE:
                            try
                            {

                                displayMessage("Player on Port " + playerPort + " is creating a new Match");
                                char[] message = new char[15];
                                message[0] = playerReader.ReadChar();
                                message = playerReader.ReadChars(15);
                                
                                
                                string matchName = "";
                                foreach (char c in message)
                                {
                                    
                                    matchName += c;
                                }
                                matchName.TrimEnd();
                                int hostPortID = Convert.ToUInt16(matchName.Substring(0,2));
                                matchName = matchName.Substring(2,matchName.Length -2);
                                displayMessage("Player on Port " + playerPort + " sent match information: HostPortID='" + hostPortID + "' MatchName='" + matchName + "'");
                                GameSession thisSession = new GameSession(hostPortID, matchName, false, playerListener);
                                gameSessions.Add(thisSession);
                                updateGameList();

                                do
                                {
                                    playerWriter.Write(Convert.ToByte(NetMessages.NULL));
                                    //loop
                                } while (!thisSession.IsFull);

                                //MessageBox.Show("Player joined this match");

                                //if here then that means the session has switched to full and we can send the player on this thread a join message to let them know the match is ready
                                playerWriter.Write(Convert.ToByte(NetMessages.JOIN));
                                //then close the stream so that the new process can work on it.
                                //do this by having the client send a terminate

                            }
                            catch(Exception any)
                            {
                                displayMessage("error trying to create a new game");
                            }
                            break;
                        case NetMessages.JOIN:
                            displayMessage("Player on Port " + playerPort + "is sending a Join Request");
                            //get the next int for the match number
                            char[] joinID = new char[2];
                            
                            //get the session information
                            //set session as full get the initiators port
                            if (!Helpers.IsNumber((char)playerReader.PeekChar()))
                            {
                                playerReader.ReadChar();
                            }
                            joinID = playerReader.ReadChars(2);
                            string joinIDString = "";
                            foreach(char c in joinID)
                            {
                                joinIDString += c;
                            }
                            int gameID = Convert.ToUInt16(joinIDString);
                            displayMessage("Player on Port " + playerPort + " joined existing match: " + gameID);

                            
                            

                            //update the game list to show it as full
                            GameSession joining = null;
                            foreach (GameSession session in gameSessions)
                            {
                                if (session.GameID == gameID)
                                {
                                    session.SetFull();
                                    joining = session;
                                    displayMessage("Set match to full on the server");
                                }
                            }
                            
                            updateGameList();
                            
                            //create a new external process
                            displayMessage("Spinning up new process on server to initiate a new Game match with Ports: " + playerPort + " and " + (gameID + defaultPort).ToString());
                            GameHost thisGame ;
                            if(joining != null)
                                thisGame = new GameHost(joining.Listener, playerPort, playerListener, gameID + defaultPort);
                            ///Thread newGame = new Thread ( new ThreadStart(new GameHost ) );

                            //bad idea causes timing issues.
                            //Process newGame = new Process();
                            //newGame.StartInfo = new ProcessStartInfo("..\\..\\..\\IW_Server_GameHost\\bin\\Debug\\IW_Server_GameHost.exe", (playerPort ).ToString() + " " + (gameID + defaultPort).ToString());
                            //newGame.Start();

                            displayMessage("Closing ports for the clients on this process so clients can connect to new process");

                            break;
                        case NetMessages.REFRESH:
                            //resend information about matches
                            displayMessage("Player on " + playerPort + " Requested list of game sessions");
                            //playerWriter.Flush();
                            string sendString;
                            foreach (GameSession session in gameSessions)
                            {
                                sendString = "";
                                if (session != null)
                                {
                                    
                                    if (session.GameID < 10)
                                    {
                                        sendString = "0";
                                        sendString += session.GameID.ToString();
                                    }
                                    else
                                        sendString = session.GameID.ToString();

                                    sendString += session.Host;
                                    if (session.IsFull)
                                    {
                                        sendString += "1";
                                    }
                                    else
                                        sendString += "0";
                                }
                                sendString = fillTransmissionString(sendString);
                                playerWriter.Write(sendString);
                            }


                            playerWriter.Write(fillTransmissionString("!0"));
                            displayMessage("Finished sending list of matches to player on Port " + playerPort);
                            break;
                        case NetMessages.TERMINATE:
                            //close socket
                            connectionActive = false;
                            clientsConnected--;
                            updateClientCounter();
                            break;
                    }
                }
                catch(IOException ioe)
                {
                   //do nothing

                }
            }

            playerReader.Close();
            playerReader = null;
            playerWriter.Close();
            playerWriter = null;
            playerConn.Close();
            playerConn = null;
            //playerListener.Stop();
            //playerListener = null;
            displayMessage("Client on Port " + playerPort + " Disconnected from the server lobby");

        }

        private string fillTransmissionString(string msg, int maxlen = 16)
        {
            for (int i = msg.Length; i < maxlen; i++)
            {
                msg += "*";
            }
            return msg;
        }


        private void startServer()
        {
            //NetworkStream socketStream;
            //BinaryWriter writer;
            //BinaryReader reader;
            //try
            //{
            //    //Trace.WriteLine("Server Started");
            //    //writeLog("server started successfully.");
                
            //    //listeners.Clear();
            //    //listeners.Add(new TcpListener(local, 8499));
            //    //writeLog("Created Listener on port 8499");

            //    //listeners[0].Start();
            //    //connection = listeners[0].AcceptSocket();
            //    //writeLog("Accepted socket connection on 8499");

            //    //socketStream = new NetworkStream(connection);
            //    //writer = new BinaryWriter(socketStream);
            //    //reader = new BinaryReader(socketStream);

            //    int currentPort = 8499;
            //    int portCount = 0;
            //    int maxPort = 8510;
            //    ThreadPool.SetMinThreads(1, 3);
                
                
            //    while (true)
            //    {
            //        if (portCount > 3)
            //        {
            //          //new Thread(listenForIncoming(currentPort);

                        
            //        }

            //    }


            //}
            //catch (Exception any)
            //{
            //    writeLog("server start failure:  " + any.Message + ": " + any.Source);

            //}


        }

        private void stopServer()
        {
            
            try
            {
                //Trace.WriteLine("Server Stopped");
                //writeLog("server stopped succesfully.");
                //foreach (TcpListener listener in listeners)
                //{
                //    listener.Stop();
                //}
                
                serverThread.Abort();
                int counter = 0;
                //listeners[0].Stop();
                while(serverThread.ThreadState == System.Threading.ThreadState.AbortRequested)
                {
                    //loop
                    if(counter == 9999999)
                    {
                        displayMessage("Waiting for server to stop");
                        counter =0;
                        serverThread.Join();
                        this.Refresh();
                    }
                    else
                        counter +=1;
                }
                
                serverThread.Start();
                
            }
            catch(Exception any)
            {
                displayMessage("server stop failure:  " + any.Message + ": " + any.Source);
                throw any;
            }
        }
        private void restartServer()
        {
            try
            {
                displayMessage("Server restart initiated");
                stopServer();
                startServer();
                displayMessage("Server restart completed.");
            }
            catch(Exception any)
            {
                displayMessage("Server restart failed.");

            }
        }


        //private void writeLog(string logText)
        //{
        //    txtServerLog.Text += "\r\n" + DateTime.Now + ":  " + logText;

        //}

        private void ServerLobby_FormClosing(object sender, FormClosingEventArgs e)
        {
            System.Environment.Exit(System.Environment.ExitCode);
            
        }

        //delegate functions
        //from Deitel
        private delegate void displayDelegate(string message);

        private void displayMessage(string message)
        {
            if (txtServerLog.InvokeRequired)
            {
                Invoke(new displayDelegate(displayMessage), new object[] { message });
            }
            else
            {
                txtServerLog.Text += "\r\n" + DateTime.Now + ":  " + message;
                
            }
        }

        private delegate void countDelegate();

        private void updateClientCounter()
        {
            if (txtClientCount.InvokeRequired)
            {
                Invoke(new countDelegate(updateClientCounter));
            }
            else
                txtClientCount.Text = clientsConnected + "";
        }

        private delegate void gameListDelegate();

        private void updateGameList()
        {
            if (lbxGameSessions.InvokeRequired)
            {
                Invoke(new gameListDelegate(updateGameList));
            }
            else
            {
                lbxGameSessions.Items.Clear();
                foreach (GameSession session in gameSessions)
                {
                    lbxGameSessions.Items.Add(session);
                }
                lbxGameSessions.Refresh();
            }
                
        }
        
    }
}
